iT邦幫忙

2025 iThome 鐵人賽

DAY 27
1

基本架構寫好後,每次加新功能都是從 use case 開始,AI 寫扣的時代,規劃架構依照重要,不然就會產生很多風格不一樣的頁面

AI 寫扣經過

  1. 建立 CategoryManagementViewModel
    data class CategoryManagementUiState(
        val categories: List<Category> = emptyList(),
        val isLoading: Boolean = true
    )
    
    @HiltViewModel
    class CategoryManagementViewModel @Inject constructor(
        private val noteUseCases: NoteUseCases
    ) : ViewModel() {
    
        val uiState: StateFlow<CategoryManagementUiState> = noteUseCases.getCategories()
            .map { CategoryManagementUiState(categories = it, isLoading = false) }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = CategoryManagementUiState(isLoading = true)
            )
    
        fun renameCategory(category: Category, newName: String) {
            viewModelScope.launch {
                noteUseCases.saveCategory(category.copy(name = newName))
            }
        }
    
        fun deleteCategory(category: Category) {
            viewModelScope.launch {
                noteUseCases.deleteCategory(category) // Need to add this use case
            }
        }
    }
    
  2. NoteUseCases 增加 DeleteCategoryUseCase
        class DeleteCategoryUseCase @Inject constructor(
            private val repository: NoteRepository
        ) {
            suspend operator fun invoke(category: Category) {
                repository.deleteCategory(category)
            }
        }
    
  3. 建立 UI 畫面
    @Composable
    fun CategoryManagementScreen(
        modifier: Modifier = Modifier,
        viewModel: CategoryManagementViewModel = hiltViewModel(),
        onNavigateBack: () -> Unit
    ) {
        val uiState by viewModel.uiState.collectAsState()
    
        CategoryManagementScreenContent(
            modifier = modifier,
            uiState = uiState,
            onRenameCategory = viewModel::renameCategory,
            onDeleteCategory = viewModel::deleteCategory,
            onNavigateBack = onNavigateBack
        )
    }
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    private fun CategoryManagementScreenContent(
        modifier: Modifier = Modifier,
        uiState: CategoryManagementUiState,
        onRenameCategory: (Category, String) -> Unit,
        onDeleteCategory: (Category) -> Unit,
        onNavigateBack: () -> Unit
    ) {
        var categoryToRename by remember { mutableStateOf<Category?>(null) }
        var categoryToDelete by remember { mutableStateOf<Category?>(null) }
    
        Scaffold(
            modifier = modifier.fillMaxSize(),
            topBar = {
                TopAppBar(
                    title = { Text("管理分類") },
                    navigationIcon = {
                        IconButton(onClick = onNavigateBack) {
                            Icon(Icons.Default.ArrowBack, contentDescription = "返回")
                        }
                    }
                )
            }
        ) { innerPadding ->
            Box(
                modifier = Modifier
                    .padding(innerPadding)
                    .fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                if (uiState.isLoading) {
                    CircularProgressIndicator()
                } else {
                    LazyColumn(modifier = Modifier.fillMaxSize()) {
                        items(uiState.categories, key = { it.id }) {
                            ListItem(
                                headlineContent = { Text(category.name) },
                                trailingContent = {
                                    Row {
                                        IconButton(onClick = { categoryToRename = category }) {
                                            Icon(Icons.Default.Edit, contentDescription = "重新命名")
                                        }
                                        IconButton(onClick = { categoryToDelete = category }) {
                                            Icon(Icons.Default.Delete, contentDescription = "刪除")
                                        }
                                    }
                                }
                            )
                        }
                    }
                }
            }
        }
    
        // Rename Dialog
        if (categoryToRename != null) {
            RenameCategoryDialog(
                category = categoryToRename!!,
                onDismiss = { categoryToRename = null },
                onConfirm = {
                    onRenameCategory(categoryToRename!!, newName)
                    categoryToRename = null
                }
            )
        }
    
        // Delete Dialog
        if (categoryToDelete != null) {
            DeleteCategoryDialog(
                category = categoryToDelete!!,
                onDismiss = { categoryToDelete = null },
                onConfirm = {
                    onDeleteCategory(categoryToDelete!!)
                    categoryToDelete = null
                }
            )
        }
    }
    
    @Composable
    private fun RenameCategoryDialog(
        category: Category,
        onDismiss: () -> Unit,
        onConfirm: (String) -> Unit
    ) {
        var newName by remember { mutableStateOf(category.name) }
    
        AlertDialog(
            onDismissRequest = onDismiss,
            title = { Text("重新命名分類") },
            text = {
                TextField(
                    value = newName,
                    onValueChange = { newName = it },
                    label = { Text("分類名稱") }
                )
            },
            confirmButton = {
                Button(onClick = { onConfirm(newName) }) {
                    Text("確定")
                }
            },
            dismissButton = {
                TextButton(onClick = onDismiss) {
                    Text("取消")
                }
            }
        )
    }
    
    @Composable
    private fun DeleteCategoryDialog(
        category: Category,
        onDismiss: () -> Unit,
        onConfirm: () -> Unit
    ) {
        AlertDialog(
            onDismissRequest = onDismiss,
            title = { Text("刪除分類") },
            text = { Text("您確定要刪除「${category.name}」嗎?\n屬於此分類的筆記將會被保留並標示為「無分類」。") },
            confirmButton = {
                Button(onClick = onConfirm) {
                    Text("刪除")
                }
            },
            dismissButton = {
                TextButton(onClick = onDismiss) {
                    Text("取消")
                }
            }
        )
    }
    
    @Preview(showBackground = true)
    @Composable
    private fun CategoryManagementScreenPreview() {
        AndroidTemplateTheme {
            CategoryManagementScreenContent(
                uiState = CategoryManagementUiState(
                    categories = listOf(Category(1, "工作", 0), Category(2, "生活", 1)),
                    isLoading = false
                ),
                onRenameCategory = { _, _ -> },
                onDeleteCategory = {},
                onNavigateBack = {}
            )
        }
    }
    
  4. 在設定頁增加進入的點擊按鈕
    Text(
        text = "資料管理",
        style = MaterialTheme.typography.titleLarge
    )
    Button(onClick = onNavigateToCategoryManagement) {
        Text("管理分類")
    }
    

上一篇
114/26 - Vibe Coding 整合 HackMD
下一篇
114/28 - Vibe Coding 建立文字轉語音 (TTS) 功能
系列文
看見筆記捲土重來,試著用 Vibe Coding 完成一款 App 吧!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
chiaominchang222
iT邦新手 5 級 ‧ 2025-10-11 12:57:52

好奇字數XD

0
chiaominchang222
iT邦新手 5 級 ‧ 2025-10-11 12:58:34

鐵人換行有點頻繁 希望可以長一點 少一點斷行XD

我要留言

立即登入留言